package com.medusa.aps.business.modules.demand.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.medusa.aps.business.common.redis.RedisUtil;
import com.medusa.aps.business.model.constant.RedisConstants;
import com.medusa.aps.business.model.enums.EquipmentStatus;
import com.medusa.aps.business.model.enums.LabelStatus;
import com.medusa.aps.business.model.enums.ProductionType;
import com.medusa.aps.business.model.enums.SchedulingStatusEnum;
import com.medusa.aps.business.model.planingscheduing.MaterialBom;
import com.medusa.aps.business.modules.basic.mp.entity.Material;
import com.medusa.aps.business.modules.basic.mp.mapper.MaterialMapper;
import com.medusa.aps.business.modules.basic.mp.mapper.WorkingCalendarMapper;
import com.medusa.aps.business.modules.basic.mp.service.MaterialService;
import com.medusa.aps.business.modules.demand.mp.entity.OrderInformation;
import com.medusa.aps.business.modules.demand.mp.entity.PlanOperationProgress;
import com.medusa.aps.business.modules.demand.mp.service.OrderInformationService;
import com.medusa.aps.business.modules.demand.service.MrpOperationService;
import com.medusa.aps.business.modules.modeling.mp.entity.*;
import com.medusa.aps.business.modules.modeling.mp.service.*;
import com.medusa.aps.business.modules.plan.mp.entity.PurchasePlan;
import com.medusa.aps.business.modules.plan.mp.entity.SuggestedPlan;
import com.medusa.aps.business.modules.plan.mp.service.PurchasePlanService;
import com.medusa.aps.business.modules.plan.mp.service.SuggestedPlanService;
import com.medusa.aps.business.common.security.resource.helper.ISecurity;
import com.medusa.aps.business.global.model.exception.GlobalException;
import jakarta.annotation.Resource;
import lombok.extern.java.Log;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDate;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author: xh.yu
 * @createTime: 2023/09/28 09:17
 */
@Service("mRPOperationService")
@Log
public class MrpOperationServiceImpl implements MrpOperationService {
    @Resource
    private OrderInformationService orderInformationService;
    @Resource
    private MaterialService materialService;
    @Resource
    private MaterialMapper materialMapper;
    @Resource
    private SuggestedPlanService suggestedPlanService;
    @Resource
    private PurchasePlanService purchasePlanService;
    @Resource
    private ProcessRouteService processRouteService;
    @Resource
    private ProcessEquipmentService processEquipmentService;
    @Resource
    private WorkingCalendarMapper workingCalendarMapper;
    @Resource
    private EquipmentService equipmentService;
    @Resource
    private WorkingProcedureService workingProcedureService;
    @Resource
    private AuxiliaryDataService auxiliaryDataService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void mrpOperation(List<OrderInformation> orderInformationList, PlanOperationProgress planOperationProgress) {
        if (CollectionUtils.isEmpty(orderInformationList)) {
            return;
        }
        orderInformationList.forEach(orderInformation -> {
            //排程清除计划延迟标签，所选中的终止排程订单没有影响
            orderInformation.removeLabelsValue(LabelStatus.PLAN_LATE);
            orderInformation.setSchedulingStatus(SchedulingStatusEnum.ARRANGING);
        });
        orderInformationService.updateBatchById(orderInformationList);
        int size = orderInformationList.size();
        for (int i = 0; i < size; i++) {
            List<SuggestedPlan> suggestedPlans = new ArrayList<>();
            List<PurchasePlan> purchasePlanes = new ArrayList<>();
            //更新计划进度
            updatePlanOperationProgress(planOperationProgress, i + 1, size);
            OrderInformation orderInformation = orderInformationList.get(i);
            //计划数量
            BigDecimal planQuantity = orderInformation.getOrderQuantity() == null ? BigDecimal.ZERO :
                    orderInformation.getOrderQuantity();
            Material orderMaterial = materialService.getOne(new LambdaQueryWrapper<Material>().eq(Material::getMaterialCode, orderInformation.getItemCode()));
            //实际库存&安全库存
            BigDecimal actualInventory = orderMaterial.getActualInventory();
            BigDecimal safetyInventory = orderMaterial.getSafetyInventory();
            if (orderMaterial.getFinishedProductRate() != null && orderMaterial.getFinishedProductRate().compareTo(BigDecimal.ZERO) > 0) {
                planQuantity = planQuantity.multiply(new BigDecimal(100)).divide(orderMaterial.getFinishedProductRate(), 0, RoundingMode.UP);
            }
            //库存逻辑校验
            if (inventoryCheck(actualInventory, safetyInventory, planQuantity)) {
                actualInventory = calculateActualInventory(actualInventory, planQuantity);
                //更新库存
                updateMaterial(actualInventory, orderMaterial);
                orderInformation.setSchedulingStatus(SchedulingStatusEnum.CLOSE);
                orderInformation.setPlannedQuantity(planQuantity);
                orderInformationService.updateById(orderInformation);
                continue;
            }
            Map<String, SuggestedPlan> suggestedPlanMap = new HashMap<>();
            //增加建议计划
            addSuggestPlan(suggestedPlanMap, suggestedPlans, orderInformation, orderMaterial,
                    orderInformation.getOrderQuantity() == null ? BigDecimal.ZERO : orderInformation.getOrderQuantity(),
                    planQuantity, 0, BigDecimal.ZERO, orderMaterial.getMaterialCode());
            orderInformation.setPlannedQuantity(planQuantity);
            orderInformationService.updateById(orderInformation);
            //bom层级展开
            List<MaterialBom> materials = materialMapper.getMaterialByMaterialCode(orderMaterial.getId());
            //校验工序设备日历信息
            checkProcessEquipmentCalendar(orderInformation, orderMaterial, materials);
            materials = materials.stream().sorted(Comparator.comparing(MaterialBom::getLevel)).collect(Collectors.toList());
            Map<Long, MaterialBom> materialBomMap = materials.stream().collect(Collectors.toMap(MaterialBom::getBomItemId, Function.identity()));
            BigDecimal subDemandQuantity;
            BigDecimal subPlanQuantity;
            //子级bom运算
            for (MaterialBom materialBom : materials) {
                String materialParentCode = getMaterialParentCode(materialBom, orderMaterial, materialBomMap);
                subPlanQuantity = getSubPlanQuantity(planQuantity, orderInformation.getOrderNumber() + materialParentCode, suggestedPlanMap).multiply(materialBom.getUnitUsage()).setScale(0, RoundingMode.UP);
                if (ProductionType.OUTSIDE_PURCHASE.equals(materialBom.getProductionType())) {
                    String[] productPurchaseUnit = materialBom.getProductPurchaseUnitRatio().split(":");
                    subPlanQuantity = subPlanQuantity.multiply(new BigDecimal(productPurchaseUnit[1])).divide(new BigDecimal(productPurchaseUnit[0]), 2, RoundingMode.UP);
                }
                subDemandQuantity = subPlanQuantity;
                //根据成品率计算计划数量
                if (materialBom.getFinishedProductRate() != null && materialBom.getFinishedProductRate().compareTo(BigDecimal.ZERO) > 0) {
                    if (ProductionType.OUTSIDE_PURCHASE.equals(materialBom.getProductionType())) {
                        subPlanQuantity = subPlanQuantity.multiply(new BigDecimal(100)).divide(materialBom.getFinishedProductRate(), 2, RoundingMode.UP);
                    }else {
                        subPlanQuantity = subPlanQuantity.multiply(new BigDecimal(100)).divide(materialBom.getFinishedProductRate(), 0, RoundingMode.UP);
                    }
                }
                BigDecimal subActualInventory = materialBom.getActualInventory();
                BigDecimal subSafetyInventory = materialBom.getSafetyInventory();
                SuggestedPlan suggestedPlan = suggestedPlanMap.get(orderInformation.getOrderNumber() + materialParentCode);
                //自制件
                if (ProductionType.SELF_MADE.equals(materialBom.getProductionType())) {
                    if (inventoryCheck(subActualInventory, subSafetyInventory, subPlanQuantity)) {
                        subActualInventory = calculateActualInventory(subActualInventory, subPlanQuantity);
                        updateMaterialBom(materialBom, subActualInventory);
                        addSuggestPlan(suggestedPlanMap, suggestedPlans, orderInformation, materialBom, subDemandQuantity, subPlanQuantity, materialBom.getLevel(), subPlanQuantity, materialParentCode, SchedulingStatusEnum.CLOSE);
                        continue;
                    }
                    if (suggestedPlan != null) {
                        addSuggestPlan(suggestedPlanMap, suggestedPlans, orderInformation, materialBom, subDemandQuantity, subPlanQuantity, materialBom.getLevel(), BigDecimal.ZERO, materialParentCode);
                    }
                    continue;
                }
                if (suggestedPlan == null || suggestedPlan.getSchedulingStatus().equals(SchedulingStatusEnum.CLOSE)) {
                    addSuggestPlan(suggestedPlanMap, suggestedPlans, orderInformation, materialBom, subDemandQuantity, subPlanQuantity, materialBom.getLevel(), BigDecimal.ZERO, materialParentCode, SchedulingStatusEnum.CLOSE);
                    continue;
                }
                if (inventoryCheck(subActualInventory, subSafetyInventory, subPlanQuantity)) {
                    subActualInventory = calculateActualInventory(subActualInventory, subPlanQuantity);
                    updateMaterialBom(materialBom, subActualInventory);
                    continue;
                }
                BigDecimal possessionInventory = subPlanQuantity;
                BigDecimal shortageQuantity = BigDecimal.ZERO;
                if (subActualInventory.compareTo(subPlanQuantity) >= 0) {
                    subActualInventory = calculateActualInventory(subActualInventory, subPlanQuantity);
                    updateMaterialBom(materialBom, subActualInventory);
                    addSuggestPlan(suggestedPlanMap, suggestedPlans, orderInformation, materialBom, subDemandQuantity, subPlanQuantity, materialBom.getLevel(), possessionInventory, materialParentCode);
                    continue;
                }
                shortageQuantity = subPlanQuantity.subtract(subActualInventory);
                possessionInventory = subActualInventory;
                subActualInventory = BigDecimal.ZERO;
                //更新子级物料信息
                updateMaterialBom(materialBom, subActualInventory);
                addSuggestPlan(suggestedPlanMap, suggestedPlans, orderInformation, materialBom, subDemandQuantity, subPlanQuantity, materialBom.getLevel(), possessionInventory, materialParentCode);
                addPurchasePlan(purchasePlanes, orderInformation, materialBom, shortageQuantity);
            }
            //保存建议计划、齐套分析、采购计划初始值
            saveMrpPlans(suggestedPlans, purchasePlanes);
        }
    }

    /**
     * 校验工序设备日历信息
     *
     * @param orderInformation 订单信息
     * @param orderMaterial    订单物料
     * @param materials        物料
     */
    private void checkProcessEquipmentCalendar(OrderInformation orderInformation, Material orderMaterial, List<MaterialBom> materials) {
        Map<String, String> materialsMap = materials.stream().collect(Collectors.toMap(MaterialBom::getMaterialCode, MaterialBom::getMaterialName));
        materialsMap.put(orderMaterial.getMaterialCode(), orderMaterial.getMaterialName());
        List<String> materialCodes = materials.stream().map(Material::getMaterialCode).collect(Collectors.toList());
        materialCodes.add(orderMaterial.getMaterialCode());
        List<ProcessRoute> processRoutes = processRouteService.list(new LambdaQueryWrapper<ProcessRoute>().in(ProcessRoute::getMaterialCode, materialCodes));
        //检查工序信息
        checkProcess(orderInformation, materialsMap, processRoutes);
        List<ProcessEquipment> processEquipments = processEquipmentService.list(new LambdaQueryWrapper<ProcessEquipment>().in(ProcessEquipment::getMaterialCode, materialCodes));
        Set<Long> processEquipmentIds = processEquipments.stream().map(ProcessEquipment::getEquipmentId).collect(Collectors.toSet());
        //检查设备日历信息
        checkEquipmentCalendar(orderInformation, materialsMap, processEquipments, processEquipmentIds);
        //检查工序设备状态
        checkProcessEquipment(orderInformation, materialsMap, processEquipments);
        //校验工序辅助资料状态
        checkProcessAuxiliaryData(orderInformation, materialsMap, processEquipments);
        //检查设备信息
        checkEquipment(orderInformation, materialsMap, processEquipments);
    }


    /**
     * 校验工序设备状态
     *
     * @param orderInformation  订单信息
     * @param materialsMap      物料Map
     * @param processEquipments 工序设备信息
     */
    private void checkProcessEquipment(OrderInformation orderInformation, Map<String, String> materialsMap, List<ProcessEquipment> processEquipments) {
        Map<String, List<ProcessEquipment>> equipmentMaterialMap = processEquipments.stream().collect(Collectors.groupingBy(ProcessEquipment::getMaterialCode));
        equipmentMaterialMap.forEach((k, v) -> {
            Map<Integer, Set<Long>> equipmentProcessMap = v.stream()
                    .collect(Collectors.groupingBy(ProcessEquipment::getProcessId, Collectors.mapping(ProcessEquipment::getEquipmentId, Collectors.toSet())));
            equipmentProcessMap.forEach((k1, v1) -> {
                List<Equipment> equipments = equipmentService.lambdaQuery().in(Equipment::getId, v1)
                        .apply("(equipment_status = {0} OR equipment_status = {1})", EquipmentStatus.IDLE.getValue(), EquipmentStatus.USING.getValue())
                        .list();
                if (equipments.isEmpty()) {
                    equipments = equipmentService.list(new LambdaQueryWrapper<Equipment>().in(Equipment::getId, v1));
                    Map<Long, Equipment> equipmentMap = equipments.stream().collect(Collectors.toMap(Equipment::getId, Function.identity()));
                    WorkingProcedure workingProcedure = workingProcedureService.getById(k1);
                    if (equipmentMap.isEmpty()) {
                        return;
                    }
                    StringBuilder result = new StringBuilder(String.format("订单【订单号%s】下的物料【%s｜%s】加工工序【%s｜%s】，对应的", orderInformation.getOrderNumber(), materialsMap.get(k), k, workingProcedure.getProcessName(), workingProcedure.getProcessCode()));
                    equipmentMap.forEach((k2, v2) -> {
                        result.append(String.format("加工设备【%s｜%s】、",
                                v2.getDeviceName(), v2.getEquipmentNumber()));
                    });
                    result.deleteCharAt(result.lastIndexOf("、"));
                    result.append("为维修中或已报废状态，无法用于排程，请在设备管理中进行配置！");
                    throw new GlobalException(result.toString());
                }
            });
        });
    }

    /**
     * 校验工序辅助资料状态
     *
     * @param orderInformation  订单信息
     * @param materialsMap      物料Map
     * @param processEquipments 工序设备信息
     */
    private void checkProcessAuxiliaryData(OrderInformation orderInformation, Map<String, String> materialsMap, List<ProcessEquipment> processEquipments) {
        processEquipments = processEquipments.stream().filter(p -> p.getAuxiliaryDataId() != null).toList();
        if (processEquipments.isEmpty()) {
            return;
        }
        Map<String, List<ProcessEquipment>> equipmentMaterialMap = processEquipments.stream().collect(Collectors.groupingBy(ProcessEquipment::getMaterialCode));
        equipmentMaterialMap.forEach((k, v) -> {
            Map<Integer, Set<Integer>> equipmentProcessMap = v.stream()
                    .collect(Collectors.groupingBy(ProcessEquipment::getProcessId, Collectors.mapping(ProcessEquipment::getAuxiliaryDataId, Collectors.toSet())));
            equipmentProcessMap.forEach((k1, v1) -> {
                List<AuxiliaryData> auxiliaryDatas = auxiliaryDataService.lambdaQuery().in(AuxiliaryData::getId, v1)
                        .apply("(status = {0} OR status = {1})", EquipmentStatus.IDLE.getValue(), EquipmentStatus.USING.getValue())
                        .list();
                if (auxiliaryDatas.isEmpty()) {
                    auxiliaryDatas = auxiliaryDataService.list(new LambdaQueryWrapper<AuxiliaryData>().in(AuxiliaryData::getId, v1));
                    Map<Integer, AuxiliaryData> auxiliaryDataMap = auxiliaryDatas.stream().collect(Collectors.toMap(AuxiliaryData::getId, Function.identity()));
                    WorkingProcedure workingProcedure = workingProcedureService.getById(k1);
                    if (auxiliaryDataMap.isEmpty()) {
                        return;
                    }
                    StringBuilder result = new StringBuilder(String.format("订单【订单号%s】下的物料【%s｜%s】加工工序【%s｜%s】，对应的", orderInformation.getOrderNumber(), materialsMap.get(k), k, workingProcedure.getProcessName(), workingProcedure.getProcessCode()));
                    auxiliaryDataMap.forEach((k2, v2) -> {
                        result.append(String.format("辅助资料【%s｜%s】、",
                                v2.getAuxiliaryResourceName(), v2.getAuxiliaryResourceCoding()));
                    });
                    result.deleteCharAt(result.lastIndexOf("、"));
                    result.append("为维修中或已报废状态，无法用于排程，请在设备管理中进行配置！");
                    throw new GlobalException(result.toString());
                }
            });
        });
    }

    /**
     * 校验设备信息
     *
     * @param orderInformation  订单信息
     * @param materialsMap      物料Map
     * @param processEquipments 工序设备信息
     */
    private void checkEquipment(OrderInformation orderInformation, Map<String, String> materialsMap, List<ProcessEquipment> processEquipments) {
        Map<String, Set<Long>> equipmentMaterialCodeMap = processEquipments.stream()
                .collect(Collectors.groupingBy(ProcessEquipment::getMaterialCode, Collectors.mapping(ProcessEquipment::getEquipmentId, Collectors.toSet())));
        equipmentMaterialCodeMap.forEach((k, v) -> {
            List<Equipment> equipments = equipmentService.lambdaQuery().in(Equipment::getId, v)
                    .apply("(equipment_status = {0} OR equipment_status = {1})", EquipmentStatus.IDLE.getValue(), EquipmentStatus.USING.getValue())
                    .list();
            if (equipments.isEmpty()) {
                equipments = equipmentService.list(new LambdaQueryWrapper<Equipment>().in(Equipment::getId, v));
                Map<Long, Equipment> equipmentMap = equipments.stream().collect(Collectors.toMap(Equipment::getId, Function.identity()));
                if (equipmentMap.isEmpty()) {
                    return;
                }
                StringBuilder result = new StringBuilder(String.format("订单【订单号%s】下的物料【%s｜%s】，", orderInformation.getOrderNumber(), materialsMap.get(k), k));
                equipmentMap.forEach((k1, v1) -> {
                    result.append(String.format("加工设备【%s｜%s】、",
                            v1.getDeviceName(), v1.getEquipmentNumber()));
                });
                result.deleteCharAt(result.lastIndexOf("、"));
                result.append("为维修中或已报废状态，无法用于排程，请在设备管理中进行配置！");
                throw new GlobalException(result.toString());
            }
        });
    }


    /**
     * 校验设备工作日历
     *
     * @param orderInformation    订单信息
     * @param materialsMap        物料bom
     * @param processEquipments   工序设备信息
     * @param processEquipmentIds 工序设备id
     */
    private void checkEquipmentCalendar(OrderInformation orderInformation, Map<String, String> materialsMap, List<ProcessEquipment> processEquipments, Set<Long> processEquipmentIds) {
        Set<Long> calendarEquipmentIds = workingCalendarMapper.selectDeviceIds(processEquipmentIds, LocalDate.now());
        if (calendarEquipmentIds == null || processEquipmentIds.size() != calendarEquipmentIds.size()) {
            if (calendarEquipmentIds != null) {
                processEquipmentIds.removeAll(calendarEquipmentIds);
            }
            Map<String, Set<Long>> equipmentMaterialCodeMap = processEquipments.stream()
                    .collect(Collectors.groupingBy(ProcessEquipment::getMaterialCode, Collectors.mapping(ProcessEquipment::getEquipmentId, Collectors.toSet())));
            List<Equipment> equipments = equipmentService.list(new LambdaQueryWrapper<Equipment>().in(Equipment::getId, processEquipmentIds));
            Map<Long, Equipment> equipmentMap = equipments.stream().collect(Collectors.toMap(Equipment::getId, Function.identity()));
            StringBuilder result = new StringBuilder();
            equipmentMaterialCodeMap.forEach((k, v) -> {
                processEquipmentIds.forEach(s -> {
                    if (v.contains(s)) {
                        result.append(String.format("订单【订单号%s】下的物料【%s｜%s】，加工设备【%s｜%s】未绑定工作日历、",
                                orderInformation.getOrderNumber(), materialsMap.get(k), k,
                                equipmentMap.get(s).getDeviceName(), equipmentMap.get(s).getEquipmentNumber()));
                    }
                });
            });
            if (result.isEmpty()) {
                return;
            }
            result.deleteCharAt(result.lastIndexOf("、"));
            result.append("未绑定工作日历，请在工作日历管理中进行配置！");
            throw new GlobalException(result.toString());
        }
    }

    /**
     * 校验工序信息
     *
     * @param orderInformation 订单信息
     * @param materialsMap     物料Map
     * @param processRoutes    工艺路线
     */
    private void checkProcess(OrderInformation orderInformation, Map<String, String> materialsMap, List<ProcessRoute> processRoutes) {
        Set<String> processMaterialCodes = processRoutes.stream().map(ProcessRoute::getMaterialCode).collect(Collectors.toSet());
        Set<String> materialCodes = materialsMap.keySet();
        materialCodes = materialService.lambdaQuery().select(Material::getMaterialCode).in(Material::getMaterialCode, materialCodes)
                .eq(Material::getProductionType, ProductionType.SELF_MADE).list().stream().map(Material::getMaterialCode).collect(Collectors.toSet());
        if (materialCodes.isEmpty()) {
            return;
        }
        if (materialCodes.size() != processMaterialCodes.size()) {
            materialCodes.removeAll(processMaterialCodes);
            if (materialCodes.isEmpty()) {
                return;
            }
            StringBuilder result = new StringBuilder();
            result.append(String.format("订单【订单号%s】下的物料", orderInformation.getOrderNumber()));
            materialCodes.forEach(k -> result.append(String.format("【%s｜%s】、", k, materialsMap.get(k))));
            result.deleteCharAt(result.lastIndexOf("、"));
            result.append("无关联工艺路线，请在工艺路线管理中进行配置！");
            throw new GlobalException(result.toString());
        }
    }

    /**
     * 获取子级计划数量
     *
     * @param planQuantity       计划数量
     * @param materialParentCode 父级
     * @param suggestedPlanMap   计划数量
     * @return 计划数量
     */
    private BigDecimal getSubPlanQuantity(BigDecimal planQuantity, String materialParentCode, Map<String, SuggestedPlan> suggestedPlanMap) {
        if ("".equals(materialParentCode)) {
            return planQuantity;
        }
        SuggestedPlan suggestedPlan = suggestedPlanMap.get(materialParentCode);
        if (suggestedPlan == null) {
            return planQuantity;
        }
        return suggestedPlan.getPlannedQuantity() == null ? BigDecimal.ZERO : suggestedPlan.getPlannedQuantity();
    }

    /**
     * 批量保存MRP计划初始值
     *
     * @param suggestedPlans  建议计划
     * @param purchasePlanes  采购计划
     */
    private void saveMrpPlans(List<SuggestedPlan> suggestedPlans, List<PurchasePlan> purchasePlanes) {
        if (CollectionUtil.isNotEmpty(suggestedPlans)) {
            suggestedPlanService.saveBatch(suggestedPlans);
        }
        if (CollectionUtil.isNotEmpty(purchasePlanes)) {
            purchasePlanService.saveBatch(purchasePlanes);
        }
    }

    /**
     * 更新计划排程进度
     *
     * @param planOperationProgress 计划排程进度model
     * @param i                     订单下标
     * @param size                  订单数
     */
    private void updatePlanOperationProgress(PlanOperationProgress planOperationProgress, int i, int size) {
        if (planOperationProgress == null) {
            return;
        }
        BigDecimal rate = new BigDecimal(i).divide(new BigDecimal(size), 2, RoundingMode.UP).multiply(new BigDecimal(20));
        planOperationProgress.setRate(rate);
        String key = RedisUtil.key(RedisConstants.PLANING_OPERATION, planOperationProgress.getId());
        RedisUtil.setCacheMap(key, planOperationProgress, Duration.ofSeconds(RedisConstants.PLANING_EXPIRE_TIME));
    }

    /**
     * 增加采购计划
     *
     * @param purchasePlans    采购计划信息list
     * @param orderInformation 订单信息
     * @param materialBom      物料bom
     * @param shortageQuantity 缺料数量
     */
    private void addPurchasePlan(List<PurchasePlan> purchasePlans, OrderInformation orderInformation, MaterialBom materialBom, BigDecimal shortageQuantity) {
        PurchasePlan plan = PurchasePlan.builder()
                .materialId(materialBom.getId())
                .itemCode(materialBom.getMaterialCode())
                .itemType(materialBom.getClassificationName())
                .orderNumber(orderInformation.getOrderNumber())
                .itemName(materialBom.getMaterialName())
                .specification(materialBom.getMaterialSpecification())
                .productionType(materialBom.getProductionType())
                .unit(materialBom.getProductUnit())
                .quantity(shortageQuantity)
                .creator(ISecurity.secureUser(ISecurity.getAuthentication(true)).getUsername())
                .build();
        if (ProductionType.OUTSIDE_PURCHASE.equals(materialBom.getProductionType())) {
            plan.setUnit(materialBom.getPurchaseUnit());
//            String[] productPurchaseUnit = materialBom.getProductPurchaseUnitRatio().split(":");
//            plan.setQuantity(shortageQuantity.multiply(new BigDecimal(productPurchaseUnit[1])).divide(new BigDecimal(productPurchaseUnit[0]), 2, RoundingMode.UP));
        }
        purchasePlans.add(plan);
    }

    /**
     * 更新物料信息
     *
     * @param materialBom     物料bom
     * @param actualInventory 实际库存
     */
    private void updateMaterialBom(MaterialBom materialBom, BigDecimal actualInventory) {
        Material material = new Material();
        BeanUtils.copyProperties(materialBom, material);
        updateMaterial(actualInventory, material);
    }

    /**
     * 更新物料信息
     *
     * @param actualInventory 实际库存
     * @param material        物料信息
     */
    private void updateMaterial(BigDecimal actualInventory, Material material) {
        material.setActualInventory(actualInventory);
        materialService.updateById(material);
    }

    /**
     * 计算实际库存
     *
     * @param actualInventory 实际库存
     * @param planQuantity    计划数量
     * @return 实际库存
     */
    private BigDecimal calculateActualInventory(BigDecimal actualInventory, BigDecimal planQuantity) {
        actualInventory = actualInventory.subtract(planQuantity);
        return actualInventory;
    }

    /**
     * 实际库存、安全库存、计划数量校验逻辑
     *
     * @param actualInventory 实际库存
     * @param safetyInventory 安全库存
     * @param planQuantity    计划适量
     * @return true or false
     */
    private boolean inventoryCheck(BigDecimal actualInventory, BigDecimal safetyInventory, BigDecimal planQuantity) {
        return actualInventory.subtract(safetyInventory).compareTo(planQuantity) >= 0;
    }

    /**
     * 增加建议计划
     *
     * @param suggestedPlanMap    建议计划map
     * @param suggestedPlans      建议计划list
     * @param orderInformation    订单信息
     * @param material            物料信息
     * @param demandQuantity      需求数量
     * @param planQuantity        计划数量
     * @param level               bom层级
     * @param possessionInventory 物料占用量
     * @param materialParentCode  父级物料编码
     */
    private void addSuggestPlan(Map<String, SuggestedPlan> suggestedPlanMap, List<SuggestedPlan> suggestedPlans, OrderInformation orderInformation, Material material, BigDecimal demandQuantity, BigDecimal planQuantity, int level, BigDecimal possessionInventory, String materialParentCode) {
        addSuggestPlan(suggestedPlanMap, suggestedPlans, orderInformation, material, demandQuantity, planQuantity, level, possessionInventory, materialParentCode, SchedulingStatusEnum.NOTARRANGED);
    }

    /**
     * 增加建议计划
     *
     * @param suggestedPlanMap    建议计划map
     * @param suggestedPlans      建议计划list
     * @param orderInformation    订单信息
     * @param material            物料信息
     * @param demandQuantity      需求数量
     * @param planQuantity        计划数量
     * @param level               bom层级
     * @param possessionInventory 物料占用量
     * @param materialParentCode  父级物料编码
     * @SchedulingStatusEnum 计划状态
     */
    private void addSuggestPlan(Map<String, SuggestedPlan> suggestedPlanMap, List<SuggestedPlan> suggestedPlans, OrderInformation orderInformation, Material material, BigDecimal demandQuantity, BigDecimal planQuantity, int level, BigDecimal possessionInventory, String materialParentCode, SchedulingStatusEnum schedulingStatusEnum) {
        SuggestedPlan suggestedPlan = SuggestedPlan.builder().
                orderId(orderInformation.getId()).
                orderNumber(orderInformation.getOrderNumber()).
                materialId(material.getId()).
                materialCode(material.getMaterialCode()).
                materialName(material.getMaterialName()).
                demandQuantity(demandQuantity).
                orderDeliveryTime(orderInformation.getOrderDeliveryDate()).
                demandTime(orderInformation.getOrderDeliveryDate()).
                plannedQuantity(planQuantity).
                immediateInventory(material.getActualInventory()).
                schedulingStatus(schedulingStatusEnum).level(level).
                materialParentCode(materialParentCode).
                manufacturingStrategy(material.getManufacturingStrategy()).
                productionType(material.getProductionType()).
                possessionInventory(possessionInventory).
                creator(ISecurity.userMust().getUsername()).
                build();
        suggestedPlanMap.put(orderInformation.getOrderNumber() + material.getMaterialCode(), suggestedPlan);
        suggestedPlans.add(suggestedPlan);
    }

    /**
     * 获取父级物料编码
     *
     * @param materialBom    物料bom
     * @param orderMaterial  父级物料
     * @param materialBomMap 物料bomMap
     * @return 父级物料编码
     */
    private String getMaterialParentCode(MaterialBom materialBom, Material orderMaterial, Map<Long, MaterialBom> materialBomMap) {
        Long parentId = materialBom.getParentId();
        String materialParentCode;
        if (parentId == null) {
            return "";
        }
        if (parentId.intValue() == 0) {
            materialParentCode = orderMaterial.getMaterialCode();
        } else {
            materialParentCode = materialBomMap.get(parentId).getMaterialCode();
        }
        return materialParentCode;
    }
}
